/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024.
 * Description: support rtos list bug on feature.
 * Author: maiziyi <maiziyi@huawei.com>
 * Create: 2024-02-05
 */
#include <linux/export.h>
#include <linux/list.h>
#include <linux/bug.h>
#include <linux/kernel.h>
#include <linux/rculist.h>
#include <linux/fs.h>
#include <linux/slab.h>

struct tst_list_info {
	struct list_head *node;
	struct list_head *next;
	struct list_head *prev;
};

#define TST_BUF_SIZE	1000

static atomic_t tst_nesting;

inline int tst_show_node(char *buf, int i, struct list_head *node, const char *node_name)
{
	struct list_head out;

	if (!node || ((unsigned long)node & 0x3)) /* align node */
		return 0;

	out.next = READ_ONCE(node->next);
	out.prev = READ_ONCE(node->prev);

	return snprintf(&buf[i], (TST_BUF_SIZE - i), "[prev:%px]<--[%s node:%px]-->[next:%px] ",
		out.prev, node_name, node, out.next);
}

static void tst_show_list(void *info)
{
	int i = 0;
	struct tst_list_info *p;
	char *buf = kmalloc(TST_BUF_SIZE * sizeof(char), GFP_KERNEL);

	if (!buf) {
		p = (struct tst_list_info *)info;
		pr_info("[list-bug-on] No enough space for saving log for cpu%d prev=%px node=%px next=%px\n",
			raw_smp_processor_id(), p->prev, p->node, p->next);
		return;
	}
	p = (struct tst_list_info *)info;
	i += snprintf(&buf[i], (TST_BUF_SIZE - i), "cpu%d:", raw_smp_processor_id());
	i += tst_show_node(buf, i, p->prev, "prev");
	if (p->node != p->prev)
		i += tst_show_node(buf, i, p->node, "current");
	if (p->prev != p->next)
		i += tst_show_node(buf, i, p->next, "next");
	i += snprintf(&buf[i], (TST_BUF_SIZE - i), "\n");
	pr_info("%s", buf);
	kfree(buf);
}

void tst_show_list_on_all_cpus(void *node, void *next, void *prev)
{
	struct tst_list_info list_info;

	list_info.node = node;
	list_info.next = next;
	list_info.prev = prev;

	if (atomic_inc_return(&tst_nesting) != 1) {
		pr_emerg("CPU%d: other core have triggered the list BUG() first. "
			 "Force enable local IRQ to make sure the first one can be finished\n",
			 smp_processor_id());
		local_irq_enable();
		return;
	}

	if (panic_on_oops)
		tst_show_list(&list_info);
	__smp_wmb();
	oops_in_progress++;
	if (panic_on_oops)
		smp_call_function(tst_show_list, &list_info, 1);
	oops_in_progress--;
}
